#include <stdio.h>
#include "aMaze-ing.h"
#include <stdlib.h>
#include <string.h>


//turns clockwise
void turnCard( Card *crd)
{
	int tmp= crd->up;
	crd->up= crd->left;
	crd->left=crd->down;
	crd->down=crd->right;
	crd->right=tmp;
}

void initClstr(Cluster* clstr)
{
	clstr->card_count=0;
	clstr->contains_fourth=0;
	clstr->contains_me=0;
	clstr->contains_second=0;
	clstr->contains_third=0;
	clstr->treasure_contains=(enum Treasure*)malloc(sizeof(enum Treasure));
	clstr->treasure_count=0;
}

void getCluster(Card** Board, int** clstr_assigned, int cnt, int i, int j, Cluster *ret)
{
	if (clstr_assigned[i][j]) return;
	clstr_assigned[i][j]=cnt;
	addCardToCluster(&(Board[i][j]),ret);
	if (i>0) if(Board[i][j].up && Board[i-1][j].down) getCluster(Board, clstr_assigned, cnt, i-1, j, ret);
	if (i<6) if(Board[i][j].down && Board[i+1][j].up) getCluster(Board, clstr_assigned, cnt, i+1, j, ret);
	if (j>0) if(Board[i][j].left && Board[i][j-1].right) getCluster(Board, clstr_assigned, cnt, i, j-1, ret);
	if (j<6) if(Board[i][j].right && Board[i][j+1].left) getCluster(Board, clstr_assigned, cnt, i, j+1, ret);
}

 Draw* calc_Draw( Card** Board, enum Treasure goal)
{
	Cluster * clstrs= ( Cluster*)malloc(sizeof( Cluster));
	Draw * ret= ( Draw*) malloc( sizeof( Draw));
	int clstr_cnt=0,i,j,mycluster=-1;
	int **clstr_assigned=(int**) malloc(sizeof(int*)*HEIGHT); // initialized in-loop, offset 1
	ret->goal_x=-1;
	ret->goal_y=-1;
	ret->winning=0;
	ret->mindif=HEIGHT+WIDTH;
	for (i=0; i<HEIGHT; i++)
	{
		clstr_assigned[i]=(int*)malloc(sizeof(int)*WIDTH);
		for (j=0; j< WIDTH; j++)
		{
			clstr_assigned[i][j]=0;
		}
	}

	for (i=0; i<HEIGHT; i++)
	{
		for (j=0; j< WIDTH; j++)
		{
			if (!clstr_assigned[i][j])
			{
				// if new cluster is to be initialized
				clstr_cnt++;
				clstrs=( Cluster*)realloc(clstrs, clstr_cnt*sizeof(Cluster));
				initClstr(&(clstrs[clstr_cnt-1]));
				getCluster( Board, (int**)clstr_assigned, clstr_cnt, i, j, &(clstrs[clstr_cnt-1]));

			}

		}
	}
	for (i=0; i<HEIGHT; i++)
	{
		for (j=0; j< WIDTH; j++)
		{
				//save Winning Card
			if (Board[i][j].treasure==goal)
			{
				ret->goal_y=i;
				ret->goal_x=j;
			}
			if (Board[i][j].contains_me)
			{
				//not_set=0;
				mycluster=clstr_assigned[i][j];
				ret->closest_y=i;
				ret->closest_x=j;
			}
		}
	}
	//find GO Coordinates, not optimised (BRUTE-FORCE)
	if (ret->goal_x>=0 && ret->goal_y >=0)
	{
		for (i=0; i<HEIGHT; i++)
		{
			for (j=0; j< WIDTH; j++)
			{
				if(mycluster==clstr_assigned[i][j])
				{
					if (abs(ret->goal_y-i)+abs(ret->goal_x-j) < ret->mindif)
					{
						ret->mindif=abs(ret->goal_y-i)+abs(ret->goal_x-j);
						ret->closest_y=i;
						ret->closest_x=j;
					}
				}
			}
		}
	}
	for (i=0; i<HEIGHT; i++)
	{
		free(clstr_assigned[i]);
	}
	free(clstr_assigned);
	ret->cluster_contains=clstrs;
	ret->cluster_count=clstr_cnt;
	return ret;
}

void addCardToCluster( Card* crd,  Cluster* clstr)
{
	clstr->card_count++;
	clstr->contains_me+=crd->contains_me;
	clstr->contains_second+=crd->contains_second;
	clstr->contains_third+=crd->contains_third;
	clstr->contains_fourth+=crd->contains_fourth;
	if (crd->treasure!=none)
	{
		clstr->treasure_count++;
		clstr->treasure_contains = (enum Treasure*) realloc(clstr->treasure_contains,sizeof(/*enum */Treasure)*(clstr->treasure_count));
		clstr->treasure_contains[clstr->treasure_count-1]=crd->treasure;
	}
}

void initNewCluster( Cluster* clstr)
{
	clstr->card_count=0;
	clstr->contains_me=0;
	clstr->contains_second=0;
	clstr->contains_third=0;
	clstr->contains_fourth=0;
	clstr->treasure_count=0;
	clstr->treasure_contains=(enum Treasure*)malloc(sizeof(enum Treasure));
}

Card** insert(Card** Board, Card* crd, int axis, int dir, int pos)
{
	int i, j,k,l,m;
	//copying Board into new ret_Board
	Card* ret_values=(Card*)malloc(HEIGHT*WIDTH*sizeof(Card));
	Card** ret_Board=(Card**)malloc(HEIGHT*sizeof(Card*));
	for (i=0; i<HEIGHT; i++) {ret_Board[i]=ret_values+i*WIDTH; memcpy(ret_Board[i],Board[i],WIDTH*sizeof(Card));}

	Card toBeInserted=*crd;
	Card toBeDropped=ret_Board[(!axis)? 2*pos+1: (!dir)*(WIDTH-1) ][(!axis)? (!dir)*(HEIGHT-1) : 2*pos+1];
	toBeInserted.contains_me+=toBeDropped.contains_me;
	toBeInserted.contains_second+=toBeDropped.contains_second;
	toBeInserted.contains_third+=toBeDropped.contains_third;
	toBeInserted.contains_fourth+=toBeDropped.contains_fourth;
	j=(!axis)? 2*pos+1: (!dir)*(WIDTH-1) ;
	k=(!axis)? (!dir)*(HEIGHT-1) : 2*pos+1 ;
	for (i=0; i<HEIGHT-1;i++)
	{
		l=(!axis)? 2*pos+1 : j+2*(dir)-1 ;
		m=(!axis)? k+2*(dir)-1 : 2*pos+1 ;
		ret_Board[j][k]=ret_Board[l][m];
		j=l;
		k=m;
	}
	ret_Board[(!axis)? 2*pos+1: (dir)*(WIDTH-1)][(!axis)? (dir)*(HEIGHT-1) : 2*pos+1]=toBeInserted;
	return ret_Board;
}

int equalsOrientation(Card* a, Card* b)
{
	return (a->left==b->left) && (a->right==b->right) && (a->down==b->down) && (a->up == b->up);
}

ret_Turn* getDraw(Card** Board, Card* crd, int axis_prev, int dir_prev, int pos_prev, enum Treasure goal)
{
	int axis, dir, pos, orientation=0, Draw_cnt=0;
	Draw* Draws=(Draw*)malloc(4*2*2*(HEIGHT/2)* sizeof(Draw)), *tmp_Draw;
	Draw ret_Draw;
	ret_Turn* ret=(ret_Turn*)malloc(sizeof(ret_Turn));
	Card** tmp_Board;
	Card tmp_crd=*crd;
	do
	{
		for (axis=0; axis<2;axis++)
		{
			for (dir=0; dir<2; dir++)
			{
				for (pos=0; pos<HEIGHT/2; pos++)
				{
					if (axis!=axis_prev || dir!=dir_prev || pos!= pos_prev )
					{
						tmp_Board=insert(Board,&tmp_crd,axis,dir,pos);
						tmp_Draw=calc_Draw(tmp_Board, goal);
						Draws[Draw_cnt]= *(tmp_Draw);
						Draws[Draw_cnt].axis=axis;
						Draws[Draw_cnt].dir=dir;
						Draws[Draw_cnt].pos=pos;
						Draws[Draw_cnt].orientation=orientation;
						Draw_cnt++;
						free(*tmp_Board);
						free(tmp_Board);
						free(tmp_Draw);
					}
				}
			}
		}
		turnCard(&tmp_crd);
		orientation++;
	}
	while (!equalsOrientation(crd,&tmp_crd));
	//tobedeleted
	ret_Draw= *evaluateDraws(Draws, Draw_cnt, goal);
	ret->axis=ret_Draw.axis;
	ret->dir=ret_Draw.dir;
	ret->pos=ret_Draw.pos;
	ret->orientation=ret_Draw.orientation;
	ret->go_x= ret_Draw.closest_x;
	ret->go_y= ret_Draw.closest_y;
	freeDraws(Draws, Draw_cnt);
	return ret;
}

// Memory Management WORKS!
Draw* evaluateDraws(Draw* Draws, int cnt, enum Treasure goal)
{
	int i,j,k, lowest_val=1000000, lowest_idx=-1,iswinning=0, progession_barrier=0;
	valuation* eval=(valuation*) malloc(sizeof(valuation)*cnt);
	Draw *tmp_Draw;
	for(i=0; i<cnt; i++)
	{
		eval[i].subject=Draws+i;
		progession_barrier+=eval[i].subject->mindif;
	}
	progession_barrier/=cnt;
	for (i=0; i< cnt; i++)
	{
		//eval[i].subject=Draws+i;
		eval[i].second_treasures=0;
		eval[i].third_treasures=0;
		eval[i].winning=0;
		tmp_Draw=eval[i].subject;
		tmp_Draw->winning=0;
		for (j=0; j<tmp_Draw->cluster_count; j++)
		{
			//If I'm in the House
			if (tmp_Draw->cluster_contains[j].contains_me)
			{
				for (k=0; k<tmp_Draw->cluster_contains[j].treasure_count; k++)
				if (goal == tmp_Draw->cluster_contains[j].treasure_contains[k]) { tmp_Draw->winning=1; eval[i].winning=1; break;}
			} 
			//If second is in the House
			if (tmp_Draw->cluster_contains[j].contains_second)
			{
				eval[i].second_treasures+=tmp_Draw->cluster_contains[j].treasure_count;
			}
			// Same goes for third
			if (tmp_Draw->cluster_contains[j].contains_third)
			{
				eval[i].third_treasures+=tmp_Draw->cluster_contains[j].treasure_count;
			}
			eval[i].value=eval[i].second_treasures*3+eval[i].third_treasures-eval[i].subject->cluster_count;
		}
		if (tmp_Draw->winning> iswinning || ( tmp_Draw->winning>= iswinning && eval[i].value < lowest_val && tmp_Draw->mindif<=progession_barrier)) 
		{
			iswinning=eval[i].winning;
			lowest_val=eval[i].value;
			lowest_idx=i;
		}
	}
	tmp_Draw=eval[lowest_idx].subject;
	free(eval);
	return tmp_Draw;
}


void freeDraws(Draw* drws, int cnt)
{
	int i,j;
	for (i=0; i< cnt; i++)
	{
		for (j=0; j<drws[i].cluster_count; j++)
		{
			free(drws[i].cluster_contains[j].treasure_contains);
		}
		free(drws[i].cluster_contains);
	}
	free(drws);
}

